﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
using Rifts.Models.Characters;

namespace Rifts.Models.Definitions
{

	/// <summary>
	/// This is used for normal races. If the race is an RCC, inherit from RccBase instead.
	/// </summary>
	public class Race
	{
		private readonly Catalog m_Catalog;
		readonly XElement m_Book;
		readonly XElement m_RaceGroupDefinition;
		readonly XElement m_RaceDefinition;

		public Race(XElement book, XElement raceGroupDefinition, XElement raceDefinition, Catalog catalog)
		{
			m_Book = book;
			m_RaceDefinition = raceDefinition;
			m_RaceGroupDefinition = raceGroupDefinition;
			m_Catalog = catalog;
		}

		public string Name
		{
			get { return m_RaceDefinition.Attribute("Name").Value; }
		}

		public string GroupName
		{
			get { return m_RaceGroupDefinition.Attribute("Name").Value; }
		}

		public virtual void ApplyAttributes(Random random, Character character)
		{
			//Apply racial group values

			foreach (var item in m_RaceGroupDefinition.XPathSelectElements("./Attributes/Attribute"))
			{
				if (!item.GetInts("Level", 1).Contains(character.Level))
					continue;

				var attributeName = item.GetAttribute("Name");
				var value = item.GetAttribute("Value");

				if (value == "Standard")
					character.SetAttribute(attributeName, AttributeBonusRoll(random));
				else
					character.SetAttribute(attributeName, random.Parse(value));

				if (attributeName == "PS" && item.HasAttribute("PSType"))
					character.PSType = item.GetEnum<PSType>("PSType");
			}

			//Apply race overrides and adds
			foreach (var item in m_RaceDefinition.XPathSelectElements("./Attributes/Attribute"))
			{
				if (!item.GetInts("Level", 1).Contains(character.Level))
					continue;

				var attributeName = item.GetAttribute("Name");
				var value = item.GetAttribute("Value");

				if (!string.IsNullOrEmpty(value))
				{
					if (value == "Standard")
						character.SetAttribute(attributeName, AttributeBonusRoll(random));
					else
						character.SetAttribute(attributeName, random.Parse(value));
				}

				var add = item.GetAttribute("Add");
				if (!string.IsNullOrEmpty(add))
					character.BumpAttribute(attributeName, random.Parse(add));

				if (attributeName == "PS" && item.HasAttribute("PSType"))
					character.PSType = item.GetEnum<PSType>("PSType");

			}

			//Apply racial group adds
			foreach (var item in m_RaceGroupDefinition.XPathSelectElements("./Attributes/Attribute"))
			{
				if (!item.GetInts("Level", 1).Contains(character.Level))
					continue;

				var attributeName = item.GetAttribute("Name");

				var add = item.GetAttribute("Add");
				if (!string.IsNullOrEmpty(add))
					character.BumpAttribute(attributeName, random.Parse(add));
			}
		}

		/// <summary>
		/// Add D6 if attrbute is 16+. If the bonus is a six, add another D6.
		/// </summary>
		/// <param name="baseScore"></param>
		/// <returns></returns>
		protected static int AttributeBonusRoll(Random random)
		{
			var baseScore = random.D6(3);
			if (baseScore < 16)
				return baseScore;
			var roll1 = random.D6();
			if (roll1 < 6)
				return baseScore + roll1;
			return baseScore + roll1 + random.D6();
		}

		public void BuildSteps(IList<BuildStep> stepList)
		{
			var nextSteps = m_RaceDefinition.Element("NextSteps");

			if (nextSteps != null)
				foreach (var element in nextSteps.Elements("Step"))
					stepList.Add(new BuildStep(element.GetAttribute("Name")));

			if (stepList.Count == 0)
				stepList.Add(new BuildStep("SelectClass"));

			//TODO: Calculate more steps from the race features
		}

		public List<CharacterClass> AllowedClasses(Character character)
		{
			var raceList = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);

			var groupNode = m_RaceGroupDefinition.Element("Classes");
			if (groupNode != null)
			{
				var defaultClass = groupNode.GetAttribute("Name");
				if (!string.IsNullOrEmpty(defaultClass))
					raceList.Add(defaultClass);
				foreach (var occ in groupNode.Elements("Class"))
				{
					raceList.Add(occ.GetAttribute("Name"));
				}
			}

			var raceNode = m_RaceGroupDefinition.Element("Classes");
			if (raceNode != null)
			{
				var defaultClass = raceNode.GetAttribute("Name");
				if (!string.IsNullOrEmpty(defaultClass))
					raceList.Add(defaultClass);
				foreach (var occ in raceNode.Elements("Class"))
				{
					raceList.Add(occ.GetAttribute("Name"));
				}
			}

			var result = from occ in m_Catalog.Classes
						 where raceList.Contains(occ.Name) || occ.AllowedRaces.Contains(Name) || occ.AllowedRaces.Contains(GroupName)
						 where occ.IsAllowed(character)
						 select occ;

			return result.ToList();
		}


		public string RaceReference
		{
			get
			{
				if (m_RaceDefinition.HasAttribute("Page"))
					return m_Book.GetAttribute("Name") + " page " + m_RaceDefinition.GetAttribute("Page");
				else
					return m_Book.GetAttribute("Name");
			}
		}

		public void Apply(Random random, Character character)
		{
			character.Race = Name;
			character.RaceReference = RaceReference;
			ApplyAttributes(random, character);
			ApplyAbilities(random, character);
			ApplyUnarmedAttacks(character);
			//TODO - Add other features
		}

		void ApplyUnarmedAttacks(Character character)
		{
			var raceGroupDefinitionElements = m_RaceGroupDefinition.Element("Abilities");
			if (raceGroupDefinitionElements != null)
				foreach (var abilityNode in raceGroupDefinitionElements.Elements("UnarmedAttack"))
				{
					if (!abilityNode.GetInts("Level", 1).Contains(character.Level))
						continue;

					character.UnarmedAttacks.Add(UnarmedAttack.Parse(abilityNode));
				}

			var raceDefinitionElements = m_RaceDefinition.Element("Abilities");
			if (raceDefinitionElements != null)
				foreach (var abilityNode in raceDefinitionElements.Elements("UnarmedAttack"))
				{
					if (!abilityNode.GetInts("Level", 1).Contains(character.Level))
						continue;

					character.UnarmedAttacks.Add(UnarmedAttack.Parse(abilityNode));
				}



		}

		void ApplyAbilities(Random random, Character character)
		{
			var raceGroupDefinitionElements = m_RaceGroupDefinition.Element("Abilities");
			if (raceGroupDefinitionElements != null)
				foreach (var abilityNode in raceGroupDefinitionElements.Elements("Ability"))
				{
					if (!abilityNode.GetInts("Level", 1).Contains(character.Level))
						continue;

					var ability = new Ability(abilityNode, random, "Racial Group: " + GroupName);

					switch (ability.Type)
					{
						case AbilityType.General:
							character.Abilities.Add(ability);
							break;
						case AbilityType.Combat:
							character.CombatAbilities.Add(ability);
							break;
					}
				}

			var raceDefinitionElements = m_RaceDefinition.Element("Abilities");
			if (raceDefinitionElements != null)
				foreach (var abilityNode in raceDefinitionElements.Elements("Ability"))
				{
					if (!abilityNode.GetInts("Level", 1).Contains(character.Level))
						continue;

					var ability = new Ability(abilityNode, random, "Race: " + Name);

					switch (ability.Type)
					{
						case AbilityType.General:
							character.Abilities.Add(ability);
							break;
						case AbilityType.Combat:
							character.CombatAbilities.Add(ability);
							break;
					}
				}


		}


	}
}